﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Xml;
using System.Xml.Linq;

using Perseus.Data;
using Perseus.Plugins;

using Hetymine.Executioner.Commands;
using Hetymine.Executioner.Controls;
using Hetymine.Executioner.Data;
using Hetymine.Executioner.Plugins;

namespace Hetymine.Executioner {
    /// <summary>
    /// Interaction logic for Config.xaml
    /// </summary>
    public partial class Config : Window {
        private IniFile _Config;        

        private ExecutionerConfigurator _ExecutionerConfigurator;
        private PluginConfigurator _PluginConfigurator;
        
        private Dictionary<string, List<ICommand>> _Commands;
        private CommandConfiguratorList _CommandConfiguratorList;
        private ListConfigurator _CommandsConfigurator;

        private Dictionary<string, IConfigurator<IniFile>> _PluginConfigurators;

        public Config() : this(new Dictionary<string,List<ICommand>>()) { }
        public Config(Dictionary<string, List<ICommand>> commands) {
            InitializeComponent();

            this.Saved = false;

            this._Config = Executioner.Config.Clone() as IniFile;
            this._Commands = commands;

            // Only show unknown tree item if unknown items exist
            if (this._Commands.ContainsKey("Unknown")) {
                TreeViewItem unknownTreeViewItem = new TreeViewItem();
                unknownTreeViewItem.Header = "Unknown";
                unknownTreeViewItem.Name = "cCommandsUnknown";
                this.cCommands.Items.Insert(0, unknownTreeViewItem);
            }
                       
            // Add command types to commands tree item
            var plugins = from p in Executioner.PluginService.Plugins
                          where p.Instance is CommandPlugin
                          select p;
            
            foreach (PluginInstance<IPlugin> p in plugins) {
                TreeViewItem pluginTreeViewItem = new TreeViewItem();
                pluginTreeViewItem.Header = p.Instance.PluginInfo.Name;
                pluginTreeViewItem.Tag = p.Name;

                this.cCommands.Items.Add(pluginTreeViewItem);                
            }
                        
            // Add plugin configuration types to plugin tree items
            this._PluginConfigurators = new Dictionary<string, IConfigurator<IniFile>>();
            plugins = from p in Executioner.PluginService.Plugins
                          where p.Instance is IConfigurable<IniFile>
                          select p;

            foreach (PluginInstance<IPlugin> p in plugins) {
                this._PluginConfigurators.Add(p.FullName, null);

                TreeViewItem pluginTreeViewItem = new TreeViewItem();
                pluginTreeViewItem.Header = p.Instance.PluginInfo.Name;
                pluginTreeViewItem.Tag = p.FullName;

                this.cPlugins.Items.Add(pluginTreeViewItem);
            }

            if (this.cPlugins.Items.Count == 0) {
                this.cNavigation.Items.Remove(this.cPlugins);
            }

            // Setup configuration events
            this.cNavigation.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(Navigation_SelectedItemChanged);

            this.cCancel.Click += new RoutedEventHandler(Cancel_Click);
            this.cOkay.Click += new RoutedEventHandler(Okay_Click);
            this.Closing += new System.ComponentModel.CancelEventHandler(Config_Closing);

            this.SelectNavigationItem(this.cExecutioner);
        }

        public bool Saved { get; protected set; }
        public IniFile Data {
            get { return this._Config; }
        }

        #region Events
        private void Okay_Click(object sender, RoutedEventArgs e) {
            this.Save();
            this.Close();
        }
        private void Cancel_Click(object sender, RoutedEventArgs e) {
            this.Close();
        }
        private void Config_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
            // Clean up before closing
            if (this._CommandsConfigurator != null) {
                this._CommandsConfigurator.Unload();                
            }
            this.cConfigurator.Child = null;
        }        
        #endregion
        #region Saving
        private void Save() {
            // Save config
            using (var sw = new StringWriter()) {
                this._Config.Save(sw);
                Executioner.Data.Save(Executioner.Config.FileName, sw.ToString());
            }
            
            // Save commands
            XmlWriterSettings xws = new XmlWriterSettings();
            xws.Encoding = System.Text.Encoding.UTF8;
            xws.Indent = true;
            xws.IndentChars = "    ";
            xws.NewLineChars = Environment.NewLine;
            xws.NewLineHandling = NewLineHandling.Replace;
            xws.OmitXmlDeclaration = true;

            string data;
            using (var sw = new StringWriter()) {
                XmlWriter xw = XmlWriter.Create(sw, xws);

                XElement xe = new XElement("Executioner");
                xe.Add(new XAttribute("Version", "1.1"));

                // Add command xelements
                XElement csxe = new XElement("Commands");
                foreach (List<ICommand> commands in this._Commands.Values) {
                    foreach (ICommand command in commands) {
                        XElement cxe = command.Save();
                        if (cxe != null) {
                            csxe.Add(cxe);
                        }
                    }
                }
                xe.Add(csxe);

                xe.WriteTo(xw);

                xw.Close();

                data = sw.ToString();
            }

            string fileName = Executioner.Directory + "commands.xml";
            Executioner.Data.Save(fileName, data);

            this.Saved = true;
        }
        #endregion
        #region Navigation
        private void Navigation_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) {            
            this.SelectNavigationItem(e.NewValue as TreeViewItem);
        }
        private void SelectNavigationItem(TreeViewItem item) {
            switch (item.Name) {
                case "cExecutioner":
                    if (this._ExecutionerConfigurator == null) {
                        this._ExecutionerConfigurator = new ExecutionerConfigurator();
                        this._ExecutionerConfigurator.InitializeConfig(this._Config);
                    }
                    this.cConfigurator.Child = this._ExecutionerConfigurator;
                    break;
                case "cCommands":
                    this.InitializeCommandConfigurator(string.Empty);
                    break;
                case "cCommandsUnknown":
                    this.InitializeCommandConfigurator("Unknown");
                    break;
                case "cCommandsTrigger":
                    this.InitializeCommandConfigurator("Trigger");
                    break;
                case "cPlugins":
                    if (this._PluginConfigurator == null) {
                        this._PluginConfigurator = new PluginConfigurator();
                        this._PluginConfigurator.InitializeConfig(this._Config);
                    }
                    this.cConfigurator.Child = this._PluginConfigurator;
                    break;
                default:
                    if (item.Parent is TreeViewItem) {
                        switch (((TreeViewItem)item.Parent).Name) {
                            case "cCommands":
                                this.InitializeCommandConfigurator(item.Tag.ToString());
                                return;
                            case "cPlugins":
                                InitializePluginConfigurator(item.Tag.ToString());
                                return;
                        }
                    }
                    this.cConfigurator.Child = null;
                    break;
            }
        }
        #endregion
        #region Commands
        private void InitializeCommandConfigurator(string type) {
            if (this._CommandsConfigurator == null) {
                this._CommandsConfigurator = new ListConfigurator();
                this._CommandsConfigurator.Load();
                this._CommandConfiguratorList = new CommandConfiguratorList(this._Commands);
                this._CommandsConfigurator.InitializeConfig(this._CommandConfiguratorList);
            }
            this._CommandConfiguratorList.FilterType(type);
            this.cConfigurator.Child = this._CommandsConfigurator;
        }        
        #endregion
        #region Plugins
        private void InitializePluginConfigurator(string type) {
            if (this._PluginConfigurators[type] == null) {
                this._PluginConfigurators[type] = ((IConfigurable<IniFile>)Executioner.PluginService[type].Instance).Configurator;
                this._PluginConfigurators[type].InitializeConfig(this._Config);
            }
            this.cConfigurator.Child = this._PluginConfigurators[type] as UserControl;
        }
        #endregion
    }
}
